package edu.cmu.cs.stage3.alice.core;

import java.io.StringReader;
import java.net.URLDecoder;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import edu.cmu.cs.stage3.alice.core.reference.DefaultReferenceResolver;
import edu.cmu.cs.stage3.alice.core.reference.PropertyReference;

public class AppletWorldLoader {

  public static final String URL_ENCODE = "ISO-8859-1";
  
  public static final String _CDATA = "_CDATA_";
  public static final String _CDATA_INIT = _CDATA+"INIT";
  public static final String _CDATA_END = _CDATA+"END";
  public static final String FIRST_CDATA_INIT = "1"+_CDATA_INIT;
  public static final String FIRST_CDATA_END = "1"+_CDATA_END;

  private static Element loadElement (String xmlString, Vector referencesToBeResolved) throws Exception {
    // System.out.println("\n\nAppletWorldLoader.java: \n"+xmlString);

    if (xmlString == null || xmlString.length() < 2)  return null;
  
    //Elimina CDATA do print pois causa parser erro
    xmlString = xmlString.replaceAll("\\<!\\[CDATA\\[", "");
    xmlString = xmlString.replaceAll("]]>", "");
  
    xmlString = xmlString.replaceAll(FIRST_CDATA_INIT, "<![CDATA[");
    xmlString = xmlString.replaceAll(FIRST_CDATA_END, "]]>");
    for (int i=2; i<20; i++) {
      xmlString = xmlString.replaceAll(i+_CDATA, i-1+_CDATA);
      }
    // System.out.println("\n\n\n\nAppletWorldLoader.java: \n"+xmlString);

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();
    InputSource is = new InputSource();
    is.setCharacterStream(new StringReader(xmlString));
    Document document = null;
    try {
      document = db.parse(is); // javax.xml.parsers.DocumentBuilder

      // Creates a parser object
      // nao tem: org.apache.xerces.parsers.DOMParser parser = new org.apache.xerces.parsers.DOMParser();
      // Sets an ErrorHandler
      // parser.setErrorHandler(new edu.cmu.cs.stage3.util.OtherErrorHandler());
      // parser.parse(xmlString);
      // document = parser.getDocument();

      // org.w3c.dom.Document parser = db.newDocument(); // javax.xml.parsers.DocumentBuilder: newDocument();

      // DOMImplementation domImplementation = documentBuilder.getDOMImplementation();

      // document = db.parse(xmlString); // strURL); // DocumentBuilder
    } catch (Exception e) {
      // edu.cmu.cs.stage3.alice.authoringtool.JAlice.setParserError(true);
      // document = new org.w3c.dom.Document(); // interface org.w3c.dom.Document
      // document.setDocumentURI(xmlString); //
      String str = ""; // 
      if (e instanceof org.xml.sax.SAXParseException) {
         //int getLineNumber(); int getColumnNumber();
         str = "lin="+((org.xml.sax.SAXParseException)e).getLineNumber()+": col="+((org.xml.sax.SAXParseException)e).getColumnNumber();
         }
       //
      System.err.println("Error: AppletWorldLoader.java: loadElement(...): failed to load some iVprog content: "+str+"\n"+e.toString());
      // Fatal Error] :2:19: Open quote is expected for attribute "class" associated with an  element type  "element".
      // Error: AppletWorldLoader.java: loadElement(...): failed to load some iVprog content: org.xml.sax.SAXParseException: Open quote is expected for attribute "class" associated with an  element type  "element".
      return null;
      }

    org.w3c.dom.Element elementNode = null;
    try {
      elementNode = document.getDocumentElement();
    } catch (Exception e) {
      String str = ""; // 
      if (e instanceof org.xml.sax.SAXParseException) {
         //int getLineNumber(); int getColumnNumber();
         str = "lin="+((org.xml.sax.SAXParseException)e).getLineNumber()+": col="+((org.xml.sax.SAXParseException)e).getColumnNumber();
         }
      System.err.println("Error: AppletWorldLoader.java: loadElement(...): failed to get some object "+document+": "+str+"\n"+e.toString());
      }
    elementNode.normalize();

    String classname = elementNode.getAttribute( "class" );
    String nameValue = elementNode.getAttribute( "name" );
    // xSystem.err.println("AppletWorldLoader.java: static Element loadElement(...): "+classname+" - "+nameValue);

    Class cls = Class.forName( classname );

    Element element = (Element)(cls.newInstance());

    if (nameValue.length() > 0) {
      element.name.set( nameValue );
      }

    org.w3c.dom.NodeList childNodeList = elementNode.getElementsByTagName( "child" );
    for (int i=0; i<childNodeList.getLength(); i++ ) {
      org.w3c.dom.Element childNode = (org.w3c.dom.Element)childNodeList.item( i );
      if (childNode.getChildNodes() == null || childNode.getChildNodes().item(0) == null) continue;
      String childString = childNode.getChildNodes().item(0).getNodeValue();
      Element child = loadElement(childString, referencesToBeResolved);
      if (child == null) continue;
      String childName = child.name.getStringValue();
      if (childName != null) {
        if (element.getChildNamed( childName ) != null) {
          child = null;
          System.err.println("Error: AppletWorldLoader.java: " + element + " already has child named " + childName + ".  skipping." );
          }
        }
      if (child != null) {
        element.addChild( child );
        }
      }

    // boolean error = false;
    org.w3c.dom.NodeList propertyNodeList = elementNode.getElementsByTagName( "property" ); // NodeList: abstract int getLength(); abstract org.w3c.dom.Node item(int);
    int length = propertyNodeList.getLength();
    for (int i=0; i<length; i++) {
      org.w3c.dom.Element propertyNode = (org.w3c.dom.Element)propertyNodeList.item( i );
      String propertyName = propertyNode.getAttribute( "name" ).trim();
      Property property = element.getPropertyNamed( propertyName );
      if ( property!=null && propertyNode!=null ) try {
        property.decode(propertyNode, referencesToBeResolved);
        } catch (Exception e) {
          String str = propertyNode.getLocalName()+","+propertyNode.getNodeName()+","+propertyNode.getNodeValue();; // propertyNode
          System.err.println("Error: AppletWorldLoader.java: loadElement(...): property "+i+"/"+length+": propertyName="+propertyName+": "+str+": "+e.toString());
          // if (!error) { e.printStackTrace(); error = true; }
          }
      }
  
    return element;
    } //  private static Element loadElement(String xmlString, Vector referencesToBeResolved)

  //Rem @SuppressWarnings("deprecation")
  public static Element loadWorld (String xmlString) throws Exception {
    xmlString = URLDecoder.decode(xmlString, URL_ENCODE);
    java.util.Vector referencesToBeResolved = new java.util.Vector();
    java.util.Vector referencesLeftUnresolved = new java.util.Vector();
    Element element = null;
    try {
      try {
        element = loadElement(xmlString, referencesToBeResolved);
        if (element==null) { //L try one more time, now with 'java.net.URLEncoder'
           // referencesToBeResolved = new java.util.Vector(); //L
           // element = loadElement(java.net.URLEncoder.encode(xmlString), referencesToBeResolved); //L
           } //L

        ReferenceResolver referenceResolver = new DefaultReferenceResolver( element, element);
        java.util.Enumeration enu = referencesToBeResolved.elements();
        while (enu.hasMoreElements()) {
          PropertyReference propertyReference = (PropertyReference)enu.nextElement();
          try {
            propertyReference.resolve( referenceResolver );
          } catch( UnresolvableReferenceException ure ) {
            referencesLeftUnresolved.add( propertyReference );
          } catch( Throwable t ) {
            System.err.println( propertyReference );
            t.printStackTrace();
            }
          }
      } catch( javax.xml.parsers.ParserConfigurationException pce ) { }
    } finally { }

    if ( referencesLeftUnresolved.size() == 0 ) {
      if (element != null) {
        element.loadCompleted();
        }
      return element;
    } else {
      PropertyReference[] propertyReferences = new PropertyReference[ referencesLeftUnresolved.size() ];
      referencesLeftUnresolved.copyInto( propertyReferences );
      throw new Exception();
      }
    }

  }
